﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using CustomConsole;
using System.Globalization;
using WpfConsole.Properties;

namespace WpfConsole
{
    /// <summary>
    /// このカスタム コントロールを XAML ファイルで使用するには、手順 1a または 1b の後、手順 2 に従います。
    ///
    /// 手順 1a) 現在のプロジェクトに存在する XAML ファイルでこのカスタム コントロールを使用する場合
    /// この XmlNamespace 属性を使用場所であるマークアップ ファイルのルート要素に
    /// 追加します:
    ///
    ///     xmlns:MyNamespace="clr-namespace:WpfConsole"
    ///
    ///
    /// 手順 1b) 異なるプロジェクトに存在する XAML ファイルでこのカスタム コントロールを使用する場合
    /// この XmlNamespace 属性を使用場所であるマークアップ ファイルのルート要素に
    /// 追加します:
    ///
    ///     xmlns:MyNamespace="clr-namespace:WpfConsole;assembly=WpfConsole"
    ///
    /// また、XAML ファイルのあるプロジェクトからこのプロジェクトへのプロジェクト参照を追加し、
    /// リビルドして、コンパイル エラーを防ぐ必要があります:
    ///
    ///     ソリューション エクスプローラで対象のプロジェクトを右クリックし、
    ///     [参照の追加] の [プロジェクト] を選択してから、このプロジェクトを参照し、選択します。
    ///
    ///
    /// 手順 2)
    /// コントロールを XAML ファイルで使用します。
    ///
    ///     <MyNamespace:ConsoleControl/>
    ///
    /// </summary>
    public class ConsoleControl : Canvas, IDisposable
    {
        static ConsoleControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ConsoleControl), new FrameworkPropertyMetadata(typeof(ConsoleControl)));
        }


        // for debug
        static int s_id = 0;
        int id = s_id++;


        internal IConsoleHost _console;
        private int _fontHeight; 
        private int _fontWidth;

        private string _cmd;

        MouseSelection _selection;

        internal ConsoleControl(IConsoleHost console, Settings settings)
        {
            Active = true;
            _fontHeight = settings.FontHeight;
            _fontWidth = settings.FontWidth;
            _cmd = settings.CmdPath;
            _console = console;

            _selection = new MouseSelection(_fontWidth, _fontHeight);
            _selection.SelectionChanged += new Selection(_selection_SelectionChanged);

            _console.ConsoleChange += OnConsoleChangeNative;
            _console.ConsoleClose += OnConsoleCloseNative;

            _collection = new VisualCollection(this);
            _grid = new VisualGrid(_collection);

            InputMethod.SetPreferredImeState(this, InputMethodState.Off);
            Focusable = true;

            _console.StartShell(_cmd, ".\\", "console", 40, 15);

            CreateCursor();
        }


        Rect _coord = new Rect();
        void _selection_SelectionChanged(Rect coord, Rect region)
        {
            RenderCursor(region);
            _coord = coord;
        }


        public ConsoleControl() : this (new ConsoleHost(), Settings.Default)
        {
        }

        public event Action ConsoleClose;

        void OnConsoleClose()
        {
            if (ConsoleClose != null)
            {
                ConsoleClose();
            }
        }

        private bool _active = false;
        public bool Active
        {
            get { return _active; }
            set 
            {
                if (!_active && value)
                {
                    UpdateVisuals();
                }
                _active = value;
            } 
        }

        void OnConsoleCloseNative()
        {
            if (Active)
            {
                Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render,
                                    (Action)OnConsoleClose);
            }
        }

        protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            base.OnPreviewKeyDown(e);

            Key k = e.Key;

            if (k == Key.Enter && _selection.HasSelection)
            {
                Rect coord = _selection.Coord;

                Clipboard.SetText(_console.RegionString((int)coord.Left, (int)coord.Top, (int)coord.Right, (int)coord.Bottom));
                _selection.Clear();
                UpdateCursor();
                return;
            }

            if (k == Key.ImeProcessed)
            {
                System.Console.WriteLine("ImeProcessed");
            }

            // why this is happen?
            if (e.Key == Key.ImeProcessed &&
                InputMethod.Current.ImeState == InputMethodState.Off &&
                e.ImeProcessedKey == Key.Space)
            {
                k = e.ImeProcessedKey;
            }

            int key = KeyInterop.VirtualKeyFromKey(k);
            if (key != 0)
            {
                _console.KeyDown(key);
                e.Handled = true;
            }
            System.Console.WriteLine("id:" + id);
        }

        public event ConsoleChangeCallback ConsoleChange;

        public void OnConsoleChange(bool resize)
        {
            _selection.Clear();
            if (ConsoleChange != null)
            {
                ConsoleChange(resize);
            }
            if (!Active)
            {
                return;
            }
            if (resize)
            {
                InvalidateCurrentVisuals();
            }
            UpdateVisuals();
        }

        private void InvalidateCurrentVisuals()
        {
            _grid.Resize(_console.Columns, _console.Rows);
        }


        private void OnConsoleChangeNative(bool resize)
        {
            if (Active)
            {
                ConsoleChangeCallback del = OnConsoleChange;
                Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render,
                    del, resize);
            }
        }


        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);

            Size size = sizeInfo.NewSize;
            int row = SizeToRows(size);
            int col = SizeToColumns(size);

            _console.SetColRow(col, row);
        }

        private int SizeToColumns(Size size)
        {
            int col = (int)(size.Width / _fontWidth);
            return col;
        }

        private int SizeToRows(Size size)
        {
            int row = (int)(size.Height / _fontHeight);
            return row;
        }

        private VisualCollection _collection;
        private VisualGrid _grid;

        private void UpdateVisuals()
        {
            if (_console == null)
                return;

            CHAR_INFO[] infos = _console.GetChars();
            for (int row = 0; row < _console.Rows; row++)
            {
                for (int col = 0; col < _console.Columns; col++)
                {
                    CHAR_INFO info = infos[_console.Columns * row + col];
                    if (_currentBuffer.MatchChar(col, row, info))
                        continue;

                    DrawingVisual vis = _grid.Visual(col, row);
                    DrawingContext dc = vis.RenderOpen();
                    FormattedText t = new FormattedText(new String((Char)info.Char.UnicodeChar, 1), CultureInfo.CurrentUICulture,
                        FlowDirection.LeftToRight, new Typeface("MS Gothic"), _fontHeight, Brushes.Black);

                    dc.DrawText(t, new Point(col * _fontWidth, row * _fontHeight));
                    dc.Close();
                }
            }
            _currentBuffer = new ConsoleBuffer() { Chars = infos, Columns = _console.Columns, Rows = _console.Rows };
            UpdateCursor();
        }

        protected override int VisualChildrenCount
        {
            get { return _collection.Count; }
        }

        // internal for test.
        internal DrawingVisual _cursorVisual; // used for cursor or selection.

        internal void CreateCursor()
        {
            _cursorVisual = new DrawingVisual();
            _cursorVisual.Opacity = 0.5;
            _collection.Add(_cursorVisual);
        }

        internal void UpdateCursor()
        {
            Rect rect = CursorRect();
            RenderCursor(rect);
        }

        internal Rect CursorRect()
        {
            Point cursorRowCol = _console.CursorPosition();
            Point pt1 = new Point(cursorRowCol.X*_fontWidth, cursorRowCol.Y*_fontHeight);
            Point pt2 = new Point(pt1.X+_fontWidth, pt1.Y+_fontHeight);
            Rect rect = new Rect(pt1, pt2);
            return rect;
        }

        private void RenderCursor(Rect rect)
        {
            using (DrawingContext dc = _cursorVisual.RenderOpen())
            {
                Pen pen = new Pen(new SolidColorBrush(Colors.Navy), 1.5);
                dc.DrawRectangle(Brushes.AliceBlue, pen, rect);
            }
        }

        protected override Visual GetVisualChild(int index)
        {
            if (index < 0 || index >= _collection.Count)
            {
                throw new ArgumentOutOfRangeException();
            }

            return _collection[index];
        }

        ConsoleBuffer _currentBuffer = new ConsoleBuffer();

        public IConsoleHost Console
        {
            get { return _console; }
        }

        internal void SetScrollPosition(int col, int row)
        {
            _console.SetScrollPosition(col, row);
        }

        protected override void OnRender(DrawingContext dc)
        {
            dc.DrawRectangle(Brushes.White, new Pen(), new Rect(0, 0, ActualWidth, ActualHeight)); // this is necesarry for hit check
            base.OnRender(dc);
        }

        protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseRightButtonDown(e);
            if (Clipboard.ContainsText())
            {
                _console.SendText(Clipboard.GetText());
                _selection.Clear();
            }
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            Focus();
            // Should I capture?
            base.OnMouseLeftButtonDown(e);

            if (IsShiftPressed())
            {
                _selection.MouseDown(e.GetPosition(this));
            }
            else
            {
                _selection.Clear();
                UpdateCursor();
            }
        }

        private static bool IsShiftPressed()
        {
            return Keyboard.GetKeyStates(Key.RightShift) == KeyStates.Down ||
                Keyboard.GetKeyStates(Key.LeftShift) == KeyStates.Down;
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            if (IsShiftPressed())
            {
                _selection.MouseMove(e.GetPosition(this));
            }
            else
            {
                _selection.Clear();
                UpdateCursor();
            }
        }

        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonUp(e);
            _selection.MouseUp(e.GetPosition(this));
        }

        #region IDisposable メンバ

        public void Dispose()
        {
            _console.Dispose();
        }

        #endregion
    }


}
